
class ContainerMarkableEdge<TValue, TEdge> {
    readonly edgeValue: TEdge;
    readonly srcNode: ContainerMarkableNode<TValue, TEdge>;
    readonly nextNode: ContainerMarkableNode<TValue, TEdge>;
    public constructor(edgeValue: TEdge, srcNode: ContainerMarkableNode<TValue, TEdge>, destNode: ContainerMarkableNode<TValue, TEdge>) {
        this.edgeValue = edgeValue;
        this.srcNode = srcNode
        this.nextNode = destNode;
    }
}

class ContainerMarkableNode<TValue, TEdge> {
    readonly value: TValue;
    readonly marked: boolean = false;
    nexts: Set<ContainerMarkableEdge<TValue, TEdge>> = new Set();

    constructor(value: TValue) {
        this.value = value;
    }
}

// function addslashes(str: any) {
//     return JSON.stringify(str).;
//     return ('' + str).replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
// }
function addslashes(string: any) {
    return (""+string).replace(/\\/g, '\\\\').
        replace(/\u0008/g, '\\b').
        replace(/\t/g, '\\t').
        replace(/\n/g, '\\n').
        replace(/\f/g, '\\f').
        replace(/\r/g, '\\r').
        replace(/'/g, '\\\'').
        replace(/"/g, '\\"');
}

/**
 * 容器盒子
 */
export class ContainerMarkableBox<TValue, TEdge> {
    edgeList: ContainerMarkableEdge<TValue, TEdge>[] = [];
    rootNode: ContainerMarkableNode<TValue, TEdge>;
    nodeList: ContainerMarkableNode<TValue, TEdge>[] = [];
    comparerValue: (a: TValue, b: TValue) => boolean;
    comparerEdge: (a: TEdge, b: TEdge) => boolean
    constructor(comparerValue: (a: TValue, b: TValue) => boolean, comparerEdge: (a: TEdge, b: TEdge) => boolean, root: TValue) {
        this.comparerValue = comparerValue;
        this.comparerEdge = comparerEdge;
        this.rootNode = new ContainerMarkableNode(root);
        this.nodeList.push(this.rootNode);
    }

    getNodeByBalue(inputedValue: TValue): ContainerMarkableNode<TValue, TEdge> | null {
        for (const node of this.nodeList) {
            if (this.comparerValue(inputedValue, node.value)) {
                return node;
            }
        }
        return null;
    }

    addNodeByEdge(srcNodeValue: TValue, targetEdgeValue: TEdge, nodeValue: TValue): boolean {
        const srcNode = this.getNodeByBalue(srcNodeValue);
        if (!srcNode) {
            return false;
        }
        const existDestNode = this.getNodeByBalue(nodeValue);
        if (existDestNode) {
            const edge = new ContainerMarkableEdge(targetEdgeValue, srcNode, existDestNode);
            srcNode.nexts.add(edge);
            this.edgeList.push(edge);
            return false;
        }

        const destNode = new ContainerMarkableNode<TValue, TEdge>(nodeValue);
        const edge = new ContainerMarkableEdge(targetEdgeValue, srcNode, destNode);
        srcNode.nexts.add(edge);
        this.nodeList.push(destNode);
        this.edgeList.push(edge);
        return true;
    }

    genGraphviz() {
        const table = this;
        let strBox = [];
        // print node
        // Node1 [shape=box label="Foo"];
        for (let curNode of table.nodeList) {
            const idx = table.nodeList.findIndex(item => item === curNode);
            strBox.push(`    I${idx} [shape=box  xlabel="I${idx}" label="${addslashes(curNode.value)}"]`);
        }
        
        // print edge
        // a -> b [color=blue];
        for (let curEdge of table.edgeList) {
            const srcIdx = table.nodeList.findIndex(item => item === curEdge.srcNode);
            const destIdx = table.nodeList.findIndex(item => item === curEdge.nextNode);
            strBox.push(`    I${srcIdx} -> I${destIdx} [label="${curEdge.edgeValue}"]`);
        }
        
        strBox.unshift("digraph G {");
        strBox.push("}");

        return strBox.join("\n")
    }
}



